﻿using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Xml.Serialization;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline;

using Tomahawk.Resources.Materials;
using System.Xml;
using System.Globalization;

namespace Tomahawk.Processors
{
    #region XML Material Importer
    /// <summary>
    /// Creates a SASMaterialCollection from an XML serialization (for debugging purposes)
    /// </summary>
    [ContentImporter(".xml", DefaultProcessor = "MaterialProcessor",
          DisplayName = "XML Material Importer - Tomahawk Engine")]
    public class XMLMaterialImporter : ContentImporter<SASEffectCollection>
    {
        public override SASEffectCollection Import(string filename, ContentImporterContext context)
        {
            SASEffectCollection output = null;

            try
            {
                using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
                {
                    XmlSerializer x = new XmlSerializer(typeof(SASEffectCollection));
                    output = (SASEffectCollection)x.Deserialize(fs);
                }
            }
            catch (InvalidContentException inner)
            {
                throw inner;
            }
            catch (Exception inner)
            {
                throw new InvalidContentException("Unknown error while importing the XML content file", inner);
            }

            return output;
        }
    }
    #endregion

    #region DAE Material Importer
    /// <summary>
    /// Creates a SASMaterialCollection from an DAE file. This files are created in the FX Composer editor
    /// that can be found here http://www.developer.nvidia.com/
    /// </summary>
    [ContentImporter(".dae", DefaultProcessor = "MaterialProcessor",
          DisplayName = "DAE Material Importer - Tomahawk Engine")]
    public class DAEMaterialImporter : ContentImporter<SASEffectCollection>
    {
        #region DAEImage
        public class DAEImage
        {
            public string id;
            public string name;
            public string path;
        }
        #endregion

        #region DAEEffect
        public class DAEEffect
        {
            public string id;
            public string name;
            public string path;
        }
        #endregion

        #region DAEParameter
        public class DAEParameter
        {
            public string type;
            public string value;
        }
        #endregion

        #region DAEMaterial
        public class DAEMaterial
        {
            public string id;
            public string name;
            public string effect;
            public Dictionary<string, DAEParameter> parameters = new Dictionary<string, DAEParameter>(StringComparer.OrdinalIgnoreCase);
        }
        #endregion

        protected Dictionary<string, DAEImage> imageTable = new Dictionary<string, DAEImage>(StringComparer.OrdinalIgnoreCase);
        protected Dictionary<string, DAEEffect> effectTable = new Dictionary<string, DAEEffect>(StringComparer.OrdinalIgnoreCase);
        protected Dictionary<string, DAEMaterial> materialTable = new Dictionary<string, DAEMaterial>(StringComparer.OrdinalIgnoreCase);

        #region Read Images
        protected bool ReadImages(XmlReader reader)
        {
            if (reader.Name == "library_images" || reader.ReadToFollowing("library_images"))
            {
                if (!reader.IsEmptyElement)
                {
                    XmlReader libraryReader = reader.ReadSubtree();
                    while (!libraryReader.EOF)
                    {
                        if (libraryReader.Name == "image" || libraryReader.ReadToFollowing("image"))
                        {
                            XmlReader imageReader = libraryReader.ReadSubtree();
                            string id = libraryReader["id"];
                            string name = libraryReader["name"];
                            if (imageReader.Name == "init_from" || imageReader.ReadToFollowing("init_from"))
                            {
                                string path = imageReader.ReadElementContentAsString();
                                DAEImage daeImage = new DAEImage();
                                daeImage.id = id;
                                daeImage.name = name;
                                daeImage.path = path;

                                if (imageTable.ContainsKey(id))
                                {
                                    throw new InvalidContentException(string.Format("The image id {0} is duplicated", id));
                                }

                                imageTable.Add(id, daeImage);
                                imageReader.ReadEndElement();
                            }
                            imageReader.Close();
                            libraryReader.ReadEndElement();
                        }
                    }
                    libraryReader.Close();
                    reader.ReadEndElement();
                }
            }

            return true;
        }
        #endregion

        #region Read Effects
        protected bool ReadEffects(XmlReader reader)
        {
            if (reader.Name == "library_effects" || reader.ReadToFollowing("library_effects"))
            {
                if (!reader.IsEmptyElement)
                {
                    XmlReader libraryReader = reader.ReadSubtree();
                    while (!libraryReader.EOF)
                    {
                        if (libraryReader.Name == "effect" || libraryReader.ReadToFollowing("effect"))
                        {
                            XmlReader effectReader = libraryReader.ReadSubtree();
                            string id = libraryReader["id"];
                            string name = libraryReader["name"];
                            string path = null;
                            while (!effectReader.EOF && path == null)
                            {
                                effectReader.ReadToFollowing("import");
                                string profile = effectReader["profile"];
                                if (profile == "fx")
                                {
                                    path = effectReader["url"];
                                }
                            }
                            if (path != null)
                            {
                                DAEEffect daeEffect = new DAEEffect();
                                daeEffect.id = id;
                                daeEffect.name = name;
                                daeEffect.path = path;

                                if (effectTable.ContainsKey(id))
                                {
                                    throw new InvalidContentException(string.Format("The effect id {0} is duplicated", id));
                                }

                                effectTable.Add(id, daeEffect);
                            }
                            effectReader.Close();
                            libraryReader.ReadEndElement();
                        }
                    }
                    libraryReader.Close();
                    reader.ReadEndElement();
                }
            }

            return true;
        }
        #endregion

        #region Read Materials
        protected bool ReadMaterials(XmlReader reader)
        {
            if (reader.Name == "library_materials" || reader.ReadToFollowing("library_materials"))
            {
                if (!reader.IsEmptyElement)
                {
                    XmlReader libraryReader = reader.ReadSubtree();
                    while (!libraryReader.EOF)
                    {
                        if (libraryReader.Name == "material" || libraryReader.ReadToFollowing("material"))
                        {
                            XmlReader materialReader = libraryReader.ReadSubtree();
                            DAEMaterial material = new DAEMaterial();
                            material.id = libraryReader["id"];
                            material.name = libraryReader["name"];

                            if (materialTable.ContainsKey(material.name))
                            {
                                throw new InvalidContentException(string.Format("The material id {0} is duplicated", material.name));
                            }

                            materialTable.Add(material.name, material);

                            if (materialReader.Name == "instance_effect" || materialReader.ReadToFollowing("instance_effect"))
                            {
                                material.effect = materialReader["url"].TrimStart(new char[] { '#' });
                                XmlReader paramReader = materialReader.ReadSubtree();
                                while (!paramReader.EOF)
                                {
                                    if (paramReader.Name == "setparam" || paramReader.ReadToFollowing("setparam"))
                                    {
                                        DAEParameter parameter = new DAEParameter();

                                        XmlReader valueReader = paramReader.ReadSubtree();
                                        valueReader.ReadToFollowing("setparam");
                                        string paramId = paramReader["ref"];

                                        valueReader.Read();
                                        string paramType = valueReader.Name;
                                        switch (paramType)
                                        {
                                            case "surface":
                                                {
                                                    parameter.type = paramType;
                                                    valueReader.ReadToFollowing("init_from");
                                                    parameter.value = !valueReader.EOF ? valueReader.ReadElementString() : "TARGET";
                                                } break;
                                            default:
                                                {
                                                    parameter.type = paramType;
                                                    parameter.value = valueReader.ReadElementString();
                                                } break;
                                        }

                                        if (material.parameters.ContainsKey(paramId))
                                        {
                                            throw new InvalidContentException(string.Format("The parameter id {0} in material {1} is duplicated", paramId, material));
                                        }

                                        material.parameters.Add(paramId, parameter);

                                        valueReader.Close();
                                        paramReader.ReadEndElement();
                                    }
                                }
                                paramReader.Close();
                                materialReader.ReadEndElement();
                            }
                            materialReader.Close();
                            libraryReader.ReadEndElement();
                        }
                    }
                    libraryReader.Close();
                    reader.ReadEndElement();
                }
            }

            return true;
        }
        #endregion

        protected SASMaterialTemplateParameter BuildSASParameter(DAEParameter param)
        {
            NumberFormatInfo nfi = new CultureInfo("en-US", false).NumberFormat;

            switch (param.type)
            {
                case "bool": return new SASMaterialTemplateParameter(SASMaterialTemplateParameter.Type.Bool) { boolValue = System.Boolean.Parse(param.value) };
                case "int": return new SASMaterialTemplateParameter(SASMaterialTemplateParameter.Type.Int) { intValue = System.Int32.Parse(param.value, nfi) };
                case "float": return new SASMaterialTemplateParameter(SASMaterialTemplateParameter.Type.Float) { floatValue = System.Single.Parse(param.value, nfi) };
                case "surface":
                    {
                        DAEImage daeImage = null;
                        if (param.value == "TARGET")
                        {
                            return null;
                        }
                        else if (imageTable.TryGetValue(param.value, out daeImage))
                        {
                            string imagePath = Path.GetDirectoryName(daeImage.path);
                            string imageFile = Path.GetFileNameWithoutExtension(daeImage.path);
                            string imageName = Path.Combine(imagePath, imageFile);

                            return new SASMaterialTemplateParameter(SASMaterialTemplateParameter.Type.Texture) { textureValue = imageName };
                        }

                        throw new InvalidContentException(string.Format("Invalid image id {0}", param.value));
                    }
                case "float2":
                    {
                        Vector4 vector = new Vector4();
                        string[] tokens = param.value.Split(new char[] { ' ' });
                        if (tokens.Length == 2)
                        {
                            vector.X = System.Single.Parse(tokens[0], nfi);
                            vector.Y = System.Single.Parse(tokens[1], nfi);
                        }
                        return new SASMaterialTemplateParameter(SASMaterialTemplateParameter.Type.Vector) { vectorValue = vector };
                    }
                case "float3":
                    {
                        Vector4 vector = new Vector4();
                        string[] tokens = param.value.Split(new char[] { ' ' });
                        if (tokens.Length == 3)
                        {
                            vector.X = System.Single.Parse(tokens[0], nfi);
                            vector.Y = System.Single.Parse(tokens[1], nfi);
                            vector.Z = System.Single.Parse(tokens[2], nfi);
                        }
                        return new SASMaterialTemplateParameter(SASMaterialTemplateParameter.Type.Vector) { vectorValue = vector };
                    }
                case "float4":
                    {
                        Vector4 vector = new Vector4();
                        string[] tokens = param.value.Split(new char[] { ' ' });
                        if (tokens.Length == 4)
                        {
                            vector.X = System.Single.Parse(tokens[0], nfi);
                            vector.Y = System.Single.Parse(tokens[1], nfi);
                            vector.Z = System.Single.Parse(tokens[2], nfi);
                            vector.W = System.Single.Parse(tokens[3], nfi);
                        }
                        return new SASMaterialTemplateParameter(SASMaterialTemplateParameter.Type.Vector) { vectorValue = vector };
                    }
                case "float4x4": 
                case "surfaceDEPTH": return null;
                default: throw new InvalidContentException(string.Format("Parameter type {0} not implemented", param.type));
            }
        }

        public override SASEffectCollection Import(string filename, ContentImporterContext context)
        {
            SASEffectCollection output = null;

            try
            {
                using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
                {
                    XmlReaderSettings settings = new XmlReaderSettings();
                    settings.IgnoreWhitespace = true;

                    XmlReader reader = XmlReader.Create(fs, settings);
                    ReadImages(reader);
                    ReadEffects(reader);
                    ReadMaterials(reader);

                    output = new SASEffectCollection();

                    foreach (KeyValuePair<string, DAEMaterial> daeMaterial in materialTable)
                    {
                        SASEffectTemplate matTemplate = new SASEffectTemplate();
                        matTemplate.name = daeMaterial.Value.name;

                        DAEEffect daeEffect = null;
                        if (effectTable.TryGetValue(daeMaterial.Value.effect, out daeEffect))
                        {
                            string effectPath = Path.GetDirectoryName(daeEffect.path);
                            string effectFile = Path.GetFileNameWithoutExtension(daeEffect.path);
                            matTemplate.effectName = Path.Combine(effectPath, effectFile);

                            foreach (KeyValuePair<string, DAEParameter> daeParam in daeMaterial.Value.parameters)
                            {
                                SASMaterialTemplateParameter sasParam = BuildSASParameter(daeParam.Value);
                                if (sasParam != null)
                                {
                                    sasParam.name = daeParam.Key;
                                    matTemplate.parameters.Add(sasParam);
                                }

                            }

                            output.collection.Add(matTemplate);
                        }
                    }
                }
            }
            catch (InvalidContentException inner)
            {
                throw inner;
            }
            catch (Exception inner)
            {
                throw new InvalidContentException("Exception while importing the DAE content file.\n" + inner.ToString(), inner);
            }

            return output;
        }
    }
    #endregion
}